extern crate chrono;
extern crate reqwest;
extern crate uuid;

use serde_json::{json, Value};
use super::backends::TmpData;
use std::str;

use log::info;
use std::thread;

use std::sync::mpsc::channel;
use std::sync::mpsc::Sender;

use super::check::{SystemInfoAndBuild, _run_cmd};
use super::config::{clear_home_output, get_plugin, get_sub_map, get_test, home_output};
use super::routes;
use super::utils::{get_uuid_ouput, output_to_file, shell};
use super::utils::{StringSplit, S};
use super::utils::StringFile;
use colored::Colorize;
use std::path::Path;
use std::string::String;
use std::collections::VecDeque;
use std::collections::HashMap;
use std::sync::Mutex;
use uuid::Uuid;

const CMD_SEP: &str = "{-cmd-sep-}";
// const CMD_PRE:&str = "{-cmd-pre-}";
// const CMD_END:&str = "{-cmd-end-}";
const TASK_END: &str = "{-task-end-}";

lazy_static! {
    static ref SENDER: Mutex<S<String>> = {
        // let pwd = get_config_password();
        let sender = CmdTask::listen();
        Mutex::new(sender)
    };
    static ref TASK_RUNNING: Mutex<HashMap<String,String>> = {        
        Mutex::new(
            HashMap::new()
        )
    };
    static ref TASK_DEQ_WAIT: Mutex<VecDeque<String>> = {        
        Mutex::new(
            VecDeque::new()
        )
    };

}

pub trait Task {
    fn create() -> String;
    fn list() -> String;
    fn option<T>(&mut self, opt: Option<T>) -> String;
    fn start(&self) -> String;
    fn stop(&self) -> String;
}

pub struct CmdTask {
    cmd: String,
    // tx: SafeSender,
    id: String,
}

impl CmdTask {
    pub fn new(cmd: &str) -> CmdTask {
        let id = Uuid::new_v4();
        CmdTask {
            cmd: String::from(cmd.trim()),
            // tx: tx,
            id: id.to_string(),
        }
    }

    fn start_or_wait(&self) -> bool{
        // let mut running_number = 0;
        if let Ok(mut now_task) = TASK_RUNNING.lock(){
            let running_number =  now_task.len();
            if running_number < 16 {
                now_task.insert(self.id.clone(),self.cmd.clone());
                return true;
            }else{
                if let Ok(mut wait_task) = TASK_DEQ_WAIT.lock(){
                    wait_task.push_back(self.cmd.clone())
                }
            }
        };
        false
    }
    fn wait_task() -> Option<String>{
        if let Ok(mut wait_task) = TASK_DEQ_WAIT.lock(){
            wait_task.pop_front()
        }else{
            None
        }
    }

    fn finish(id:&str) -> usize{
        

        if let Ok(mut now_task) = TASK_RUNNING.lock(){
            if now_task.contains_key(id){
                if let Some(mut obj) = id.uuid_to_object(){
                    let cmd = now_task.get(id).unwrap();
                    obj.insert("status","Finish");
                    obj.insert("cmd", &cmd);
                    obj.save();
                }
                now_task.remove(id);
            }
            now_task.len()
        }else{
            0
        }
    }

    fn get_output_path(&self) -> String {
        Path::new(&home_output())
            .join(&self.id)
            .to_str()
            .expect("cast str error")
            .to_string()
    }

    pub fn new_plugin(cmd: &str, version: Option<&str>) -> CmdTask {
        let raw = cmd.to_string();
        let id = Uuid::new_v4();
        let (exe_name, exe_body) = raw.split_once(":");
        let exe = get_plugin(&exe_name, version);
        if exe.len() == 0 {
            CmdTask {
                cmd: String::from(""),
                // tx: tx,
                id: id.to_string(),
            }
        } else {
            log::info!("ready cmd: {} |{}", exe.green(), exe_body.yellow());
            let runner_cmd = exe.replace("[T]", exe_body.trim());
            log::info!(
                "ready cmd: {}|{} | {}",
                exe.green(),
                exe_body.yellow(),
                runner_cmd.green()
            );
            CmdTask {
                cmd: runner_cmd,
                // tx: tx,
                id: id.to_string(),
            }
        }
    }

    fn get_id(&self) -> String {
        self.id.clone()
    }

    fn get_sender(&self) -> S<String> {
        let s = SENDER.lock().expect("unlock sender failed!");
        s.clone()
    }

    pub fn listen() -> Sender<String> {
        let (tx, rx) = channel::<String>();
        // let dbs = Arc::new(db);

        // let db_h = Arc::clone(&dbs);
        thread::spawn(move || loop {
            let out = match rx.recv() {
                Ok(a) => a,
                _ => break,
            };
            if &out == TASK_END {
                break;
            }
            if out.contains(CMD_SEP) {
                let (id, output_string) = out.split_once(CMD_SEP);
                info!("cmd : ok | {}", id.green());
                output_to_file(&id, output_string.as_bytes());
                CmdTask::finish(&id);
                TmpData::insert(&id, json!("Finish"));
                
            } else {
                match Uuid::parse_str(&out) {
                    Ok(_) => {
                        CmdTask::finish(&out);
                        TmpData::insert(&out, json!("Finish"));


                        if let Some(new_cmd) = CmdTask::wait_task(){
                            let new_cmd_task = CmdTask{
                                cmd:new_cmd,
                                id:Uuid::new_v4().to_string()
                            };
                            new_cmd_task.start();
                        }

                    }
                    Err(_) => {}
                }
            }
        });
        tx
    }

    fn start(&self) {
        if self.cmd.len() == 0 as usize {
            return;
        }

        TmpData::insert(&self.get_id(), json!("Running"));
        let cmd = self.cmd.clone();
        info!("start cmd task: {}", cmd.green());

        let tx = self.get_sender();
        let id = self.get_id();
        let _ = id.uuid_to_new_object();
        let sep = CMD_SEP.to_string().clone();

        let output_path = self.get_output_path();
        let cmd_shared = cmd.clone();
        if self.start_or_wait(){
            thread::spawn(move || {
                let _ = shell(cmd_shared, id.to_string(), sep, Some(output_path));
                let _ = tx.send(id);
            });
        }
        
    }
}

pub fn task_protocol(para: &routes::Parameter, info: &SystemInfoAndBuild) -> (i32, String) {
    let (op, data, _) = para.get_args_str();
    let cmd_task: CmdTask;
    let code: i32;
    let msg = match op {
        "cmd" => {
            cmd_task = CmdTask::new(data);
            cmd_task.start();
            code = 0;

            json!({
                "id" : cmd_task.id,
                "status": TmpData::query(&cmd_task.get_id()).to_string()
            })
            .to_string()
        }
        "clear" => {
            code = 0;
            let s = clear_home_output();
            TmpData::clean();
            match s {
                Ok(a) => a,
                _ => "no string output".to_string(),
            }
        }
        "view" => {
            code = 0;
            // let v:Value = db.clone().lock().expect("unlock fail")
            //     .fuzzy(data).into();
            let v = get_uuid_ouput(data);
            v
            // db.clone().lock().expect("unlock failed").remove(data);
            // v.to_string()
        }
        "search" => {
            code = 0;
            let v: Value = TmpData::fuzzy(data).into();
            v.to_string()
        }
        "plugin" => {
            if data.contains(":") {
                code = 0;

                cmd_task = CmdTask::new_plugin(data, Some(&info.get_python()));
                if cmd_task.cmd.len() == 0 {
                    return (2, format!("not valid paylod :{} ", data));
                }
                cmd_task.start();
                json!({
                    "id" : cmd_task.id,
                    "status": TmpData::query(&cmd_task.get_id()).to_string()
                })
                .to_string()
            } else if data.contains("?") {
                code = 0;
                let mut cmd = get_test(&data.replace("?", "").trim());
                if cmd.contains("[PY]") {
                    cmd = cmd.replace("[PY]", &info.get_python());
                }
                info!("? :{} ", &cmd.yellow());
                _run_cmd(&cmd)
            } else {
                code = 1;
                "not valid paylod  ".to_string()
            }
        }
        "plugins" => {
            code = 0;
            let map = get_sub_map("plugins");
            let mut buf = String::new();
            let _ = map
                .iter()
                .map(|(k, v)| {
                    buf.push_str(&format!("[{}]  {}\n", k, v));
                })
                .collect::<Vec<()>>();
            buf
        }
        "ls" => {
            code = 0;
            let vs = TmpData::items("[uuid]");
            let mut json_buf = String::from("\n");
            let _ = vs
                .iter()
                .map(|t_val| {
                    json_buf.push_str(&format!("{}: {} \n", &t_val.0, &t_val.1));
                })
                .collect::<Vec<()>>();
            json_buf.clone()
        }
        _ => {
            code = 2;
            r#"
            cmd:  to run cmd  like 'ls /tmp && echo I'am ok'  
            search:  search some result by key.
            view:     extract some result by key, value will be remove.
            clear:   clear all data.
            ls:  to see all tasks' status.
            plugins: show all plugin config.
            plugin:  run plugin like  'plugin_name: target' 
                                                  |                           
                                        sep with  ^     
            "#
            .to_string()
        }
    };
    (code, msg)
}
